package se.gala.core.abstracts;

import java.util.Arrays;

import se.gala.core.DrawId;
import se.gala.core.GameId;
import se.gala.core.GameSessionId;
import se.gala.rng.RNG;
import se.gala.utils.GGObject;

public abstract class AbstractGame extends GGObject {

	protected RNG rng;
	private AbstractDrawSymbol[] uniqueSymbols;
	private AbstractDrawSymbol[] distributedSymbols;
	protected int uniqueSymbolCount;
	protected int distributedSymbolsArraySize;
	/**
	 * The winning in cent amount. E.g. "102" means "1.02"
	 */
	protected int winning;
	private PrecisionDegree precision;

	protected AbstractPaytable paytable;

	/**
	 * The last result.
	 */
	protected AbstractGameResult result;

	/**
	 * Indicates if the odds are fixed (true), or dependent on player game
	 * pattern.
	 */
	private boolean isFixedOddsGame;
	protected GameSessionId gameSessionId;
	protected GameId gameId;

	/**
	 * 
	 * @param isFixedOddsGame
	 *            Indicates if the odds are fixed (true), or dependent on player
	 *            game pattern.
	 */
	AbstractGame(boolean isFixedOddsGame) {
		this.rng = RNG.gameRNGFactory();
		init(isFixedOddsGame);
	}

	/**
	 * Use this constructor to replay games.
	 * @param isFixedOddsGame
	 *            Indicates if the odds are fixed (true), or dependent on player
	 *            game pattern.
	 * @param historicRNGSeed The seed used to initiate the RNG.
	 */
	protected AbstractGame(boolean isFixedOddsGame, long historicRNGSeed) {
//		Thread rngThread = new Thread();
		this.rng = RNG.historicGameRNGFactory(historicRNGSeed);
		init(isFixedOddsGame);
	}
	
	protected abstract AbstractGame historicGame(long historicRNGSeed, Object params[]);
	
	private final void init(boolean isFixedOddsGame) {
		setNewGameSession();
		this.isFixedOddsGame = isFixedOddsGame;
		setPaytable();
	}
	
	protected final void setNewGameSession() {
		gameSessionId = new GameSessionId(rng, this);
	}
	
	public final GameSessionId getGameSessionId() {
		return this.gameSessionId;
	}

	/**
	 * Returns the paytable with the winning combinations.
	 * 
	 * @return
	 */
	public final AbstractPaytable getPaytable() {
		return paytable;
	}

	public final AbstractDrawSymbol[] getDistributtedSymbols() {
		return distributedSymbols;
	}

	public final AbstractDrawSymbol[] getUniqueSymbols() {
		return uniqueSymbols;
	}

	/**
	 * Indicates if the odds are fixed (true), or dependent on player game
	 * pattern.
	 * 
	 * @return
	 */
	protected final boolean isFixedOddsGame() {
		return isFixedOddsGame;
	}

	protected final DrawId generateDrawId() {
		return new DrawId(rng);
	}

	protected void setResult(AbstractGameResult result) {
		this.result = result;
	}

	/**
	 * Returns the last result.
	 * 
	 * @return
	 */
	public AbstractGameResult getResult() {
		return result;
	}

	abstract public void playGame();
	abstract public void placeBet(AbstractBet bet);
	
	abstract protected DrawId doDraw();
	
	protected void setGameId() {
		this.gameId = new GameId(rng, this);
	}
	
	public GameId getGameId() {
		return this.gameId;
	}

	abstract protected void setPaytable();

	abstract protected void setSymbols();

	protected void initSymbols(int symbolCount, PrecisionDegree precision) {
		this.uniqueSymbolCount = symbolCount;
		this.precision = precision;
		this.distributedSymbolsArraySize = symbolCount * precision.getDegree();

		uniqueSymbols = new AbstractDrawSymbol[symbolCount];
		distributedSymbols = new AbstractDrawSymbol[distributedSymbolsArraySize];
	}

	private int currentDistributtedIndex = 0;
	private int currentUniqueIndex = 0;

	synchronized protected void addSymbol(int index, AbstractDrawSymbol s,
			float probability) {
		uniqueSymbols[index] = s;
		currentUniqueIndex = index + 1;
		int distributedIndex = currentDistributtedIndex
				+ Math.round(distributedSymbolsArraySize * probability);
//		log("index: " + distributedIndex);
		Arrays.fill(distributedSymbols, currentDistributtedIndex,
				distributedIndex, s);
		currentDistributtedIndex = distributedIndex;
	}

	protected final boolean ditributtedSymbolsProperlyInitialised() {
		if (currentUniqueIndex < uniqueSymbolCount) {
			throw new Error(
					"Game initiation Error. $uniqueSymbols not initialized. $currentUniqueIndex is: "
							+ currentUniqueIndex
							+ ", while target is: "
							+ uniqueSymbolCount);
		}
		if (currentDistributtedIndex < distributedSymbolsArraySize) {
			throw new Error(
					"Game initiation Error. $distributedSymbols not initialized. $currentDistributtedIndex is: "
							+ currentDistributtedIndex
							+ ", while target is: "
							+ distributedSymbolsArraySize);
		}

		return true;
	}

	public final int getSymbolCount() {
		return uniqueSymbolCount;
	}

	protected final PrecisionDegree getDistributionPrecision() {
		return precision;
	}
	
	abstract protected void setWinning();
	
	public final int getWinning() {
		return winning;
	}
	
	public final RNG getRNG() {
		return rng;
	}
}